import { computed, inject, isReactive, isRef, reactive, toRefs, watch } from "vue";
import { activePinia, piniaSymbol, setActivePinia } from "./index";
import { isComputed, isObject, isFunction, publisher, subscriber } from "./util";
import type { OptionsInter, piniaInter, SetupType } from "./index";

// defineStore的参数可能是： 1. id + options 2. id + setup
export const defineStore = (id: string, options: SetupType | OptionsInter) => {
    // 方法列表
    const actionList: Function[] = [];
    const useStore = () => {
        let pinia: piniaInter | undefined = inject(piniaSymbol);
        if(pinia) {
            setActivePinia(pinia)
        }
        pinia = activePinia!;
        const apiStore = {
            $patch(objectOrFunction: Record<string, any> | ((state: Record<string, any>) => Record<string, any>)) {
                // 递归合并对象
                const mergeObject = (target: Record<string, any>, state: Record<string, any>) => {
                    for(const key in state) {
                        const oldValue = target[key];
                        const newValue = state[key];
                        if(isObject(oldValue) && isObject(newValue)) {
                            target[key] = mergeObject(oldValue, newValue);
                        } else {
                            target[key] = newValue;
                        }
                    }
                    return target;
                }

                if(typeof objectOrFunction === "object") {
                    mergeObject(pinia?.state[id], objectOrFunction);
                } else {
                    objectOrFunction(pinia?.state[id]);
                }
            },
            // 监控状态state变化
            $subscribe(callback: Function, options: Record<string, any> = {}) {
                watch(pinia?.state[id], (state) => {
                    callback(id, state)
                }, options)
            },
            // 监控方法action变化，是一个发布订阅模式
            $onAction: publisher.bind(null, actionList)
        }
        const store = reactive<Record<string, any>>(apiStore);
        // 如果没有当前的store，则创建，如果有，则直接返回已经创建好的
        if (!pinia._store.has(id)) {
            if (typeof options === "function") {
                createSetupStore(id, options, pinia, store, actionList)
            } else {
                createOptionsStore(id, options, pinia, store, actionList);
            }
            store.$id = id;
            pinia._store.set(id, store);

            // 创建时调用插件
            pinia._plugin.forEach(plugin => {
                // 将插件的返回值添加到store中
                Object.assign(store, plugin({ store }));
            })
        }

        console.log(pinia, store)
        return pinia._store.get(id)!
    }

    return useStore;
}

const createOptionsStore = (id: string, options: OptionsInter, pinia: piniaInter, store: Record<string, any>, actionList: Function[]) => {
    const { state, getters = {}, actions = {} } = options;

    pinia.state[id] = state ? state() : {};
    const localState = toRefs(pinia.state[id]); // 将localState转化为ref

    // 绑定this
    const localGetter: Record<string, any> = {};
    for (let key in getters) {
        localGetter[key] = computed(() => {
            return getters[key].call(store, store);
        })
    }
    const localAction: Record<string, any> = {};
    for (let key in actions) {
        localAction[key] = function () {
            actionHandler(actionList, actions[key], store, arguments);
        }
    }

    Object.assign(store, localState, localGetter, localAction);
    store.$reset = () => {
        const oldState = state ? state() : {};
        store.$patch((state: Record<string, any>) => {
            Object.assign(state, oldState);
        })
    }
}

const createSetupStore = (id: string, setup: SetupType, pinia: piniaInter, store: Record<string, any>, actionList: Function[]) => {
    const setupObject = setup();
    pinia.state[id] = {};

    for (let key in setupObject) {
        const prop = setupObject[key];
        // state
        if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
            pinia.state[id][key] = prop
        }
        // action
        if(isFunction(prop) && !isComputed(prop)) {
            setupObject[key] = function () {
                actionHandler(actionList, prop, store, arguments);
            }
        }
    }

    Object.assign(store, setupObject);
}

const actionHandler = (actionList: Function[], action: Function, store: Record<string, any>, args: IArguments) => {
    const afterCallbackList: Function[] = [];
    const onErrorCallbackList: Function[] = [];
    const after = (callback: Function) => afterCallbackList.push(callback);
    const onError = (callback: Function) => onErrorCallbackList.push(callback);

    subscriber(actionList, { after, onError })

    try{
        const result = action.apply(store, args);
        if(result instanceof Promise) {
            result.then((value) => {
                subscriber(afterCallbackList, value)
            }).catch(error => {
                subscriber(onErrorCallbackList, error)
            })
        } else {
            subscriber(afterCallbackList, result)
        }
        return result;
    } catch(error) {
        subscriber(onErrorCallbackList, error)
    }
}